#include "GBFreeType.h"
#include "..\RenderComponents.h"
#include <io.h>
#include <glee.h>
#include <gl.h>
#include <algorithm>

// Anonymous, non-member related funcs
using MeshComponent::Triangle;

struct ColorRange
{
	RenderComponent::Color color;
	int begin;
	int end;
	std::string text;

	ColorRange() : begin(-1), end(-1) { }
	~ColorRange() { }
};

//void ExtractColorValues(const char* szText, std::deque<ColorRange>& colors)
//{
//	// Example:
//	// '<c:0.82, 0.82, 0.24, 1.0f>cdlcosdc</c>'
//	char* string = (char*)szText;
//	unsigned currLetter = 0;
//	ColorRange defaultColor;
//
//	for (; *string; string++)
//	{
//		if(*string == '<')
//		{
//			currLetter++;
//			if(*++string == 'c')
//			{
//				currLetter++;
//				if(*++string == ':')
//				{
//					currLetter++;
//					++string;
//
//					// Figure out color values
//					// float(,) float(,) float(,) float
//					char floats[4][4];
//					ColorRange newColor;	// Default color is white
//					for (unsigned j = 0; j < 4; j++)
//					{
//						// Extract the floats first from the string
//						if(strlen(string)+1 >= 3)
//						{
//							std::string copyString = string;
//							copyString.erase(3, strlen(string)+1);
//							strcpy_s(floats[j], 4, copyString.c_str());
//							newColor.color[j] = atof(floats[j]);
//
//							// for commas (,)
//							string = (j < 3) ? string + 5 : string + 4; // Setup pointer to the next float
//							currLetter += 5;
//						}
//					}
//
//					// Now iterate through the remaining string till we hit a '</c>' delim
//					newColor.begin = currLetter;
//					for (; *string; string++)
//					{
//						if(*string == '<')
//						{
//							currLetter++;
//							if(*++string == '/')
//							{
//								currLetter++;
//								if(*++string == 'c')
//								{
//									currLetter++;
//									if(*++string == '>')
//									{
//										currLetter++;
//										++string;
//										break;
//									}
//								}
//							}
//						}
//
//						newColor.text.append(1, *string);
//						currLetter++;
//					}
//
//					// Push back the default color
//					if(defaultColor.text != "")
//					{
//						colors.push_back(defaultColor);
//
//						// Reset defaultColor to a new construct of itself
//						defaultColor = ColorRange();
//					}
//
//					newColor.end = currLetter;
//					colors.push_back(newColor);
//				}
//			}
//		}
//
//		if(*string == 0)
//			break;
//
//		defaultColor.text.append(1, *string);
//		currLetter++;
//	}
//
//	// Handle the case if the default color is not pushed back
//	if(defaultColor.text != "")
//		colors.push_back(defaultColor);
//}

void AdjustPt(long& pt, long minHeight, long maxHeight)
{
	long adjPt = abs(pt);

	// Find the min
	if(adjPt < minHeight)
		pt = (pt > 0) ? minHeight : minHeight * -1;

	// Find the max
	if(adjPt > maxHeight)
		pt = (pt > 0) ? maxHeight : maxHeight * -1;
}

unsigned MulPow2(unsigned unNum)
{
	unsigned unNewNum = 1;
	while(unNewNum < unNum) 
		unNewNum <<= 1;
	return unNewNum;
}

GBFreeType::GBFreeType()
{
	Initialize();
}

GBFreeType::~GBFreeType()
{
	Shutdown();
}

void GBFreeType::Initialize()
{
	FT_Init_FreeType(&this->m_library);
}

void GBFreeType::Shutdown()
{
	//// Destroy all the faces
	unsigned unNumFaces = (unsigned)m_faces.size();
	for (unsigned i = 0; i < unNumFaces; i++)
		DestroyFace(i);

	FT_Done_FreeType(this->m_library);
}

int GBFreeType::CreateFace(const char* szFilename, long pt)
{
	// The file cannot be found or opened
	if(_access(szFilename, 0) == -1) return -1;

	// Local ID to use for the return
	int nID = 0;

	// Find a face that is unused (if there is one)
	FacesIter iter = m_faces.begin();
	GBFace* pFace = NULL;
	GBFace newFace;	// Only if there is a new face pFace will == this

	for(; iter != m_faces.end(); iter++)
	{
		if(!(*iter).used)
		{
			pFace = &(*iter);	
			break;
		}

		++nID;
	}

	if(m_faces.empty() || pFace == NULL)
		pFace = &newFace;

	// Check if the file exists before we even try to access it.
	// We didn't make it
	if(FT_New_Face(m_library, szFilename, 0, &pFace->face))
		return -1;

	// Set up width/height
	// Note: FreeType measures font heights by 64ths of pixels so we must mult by 64
	//		 To get the correct height

	// Figure out the 'real' computationally - good size for the machine
	pFace->pt = pt;
	AdjustPt(pFace->pt, GB_MIN_FONT_SIZE, GB_MAX_FONT_SIZE);

	long size = abs(pt) << 6;
	FT_Set_Char_Size(pFace->face, size, size, 96, 96);

	m_faces.push_back(*pFace);

	if(pFace == &newFace)
		return (int)(m_faces.size() - 1);

	// Return the old ID
	return nID;
}

void GBFreeType::DestroyFace(int nID)
{
	if(nID >= (int)m_faces.size() || m_faces.empty() || nID < 0) return;

	// This font doesn't need to be destroyed, it already is
	if(!m_faces[nID].used) return;

	// Delete it, clean memory
	glDeleteLists(m_faces[nID].nLists[0], GB_NUM_CHARS);
	glDeleteTextures(GB_NUM_CHARS, m_faces[nID].nTextures);

	FT_Done_Face(m_faces[nID].face);
	m_faces[nID].used = false;	// Set it to not currently used
}

void GBFreeType::CacheFace(const int nID, unsigned char begin, unsigned char end)
{
	if(!IsAvailable(nID)) return;

	begin = Min(begin, end);
	end = Max(begin, end);

	glPushClientAttrib(GL_ENABLE_BIT);

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glDisableClientState(GL_INDEX_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);

	glPushMatrix();
	{
		// Go thru the range of characters
		for(unsigned char i = begin; i <= end; i++)
			CacheGlyph(m_faces[nID], i);
	}
	glPopMatrix();

	glBindTexture(GL_TEXTURE_2D, -1);
	
	glPopAttrib();
}

int GBFreeType::CacheGlyph(GBFace& face, unsigned char ch)
{
	// Glyph is already cached... just retrieve that old one
	if(face.nLists[ch] >= 0)
		return face.nLists[ch];

	if(FT_Load_Glyph(face.face, FT_Get_Char_Index(face.face, ch), FT_LOAD_DEFAULT))
		return -1;

	FT_Glyph glyph;
	if(FT_Get_Glyph(face.face->glyph, &glyph))
		return -1;

	FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1);
	FT_BitmapGlyph bitmapGlyph = (FT_BitmapGlyph)glyph;
	FT_Bitmap* bitmap = &bitmapGlyph->bitmap;

	int nWidth = MulPow2(bitmap->width);
	int nHeight = MulPow2(bitmap->rows);

	// Convert to texture memory (readable by OGL)
	unsigned char* szGlyphData = new unsigned char[(nWidth * nHeight) * 2];

	for (int i = 0; i < nWidth; i++)
	{
		for (int j = 0; j < nHeight; j++)
		{
			// Alpha-tize the rasterized grayscaled image (0xff000000)
			szGlyphData[2 * (i + j * nWidth)] = szGlyphData[2 * (i + j * nWidth) + 1] = 
				(i >= bitmap->width || j >= bitmap->rows) ? 
				0xff000000 : bitmap->buffer[i + bitmap->width * j];
		}
	}

	// Time to create the display/texture Lists (1)
	glGenTextures(1, &face.nTextures[ch]);
	glBindTexture(GL_TEXTURE_2D, face.nTextures[ch]);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR/*_MIPMAP_LINEAR*/);	// GL_NEAREST - Blockier, Fast
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR/*_MIPMAP_LINEAR*/);	// GL_NEAREST - Blockier, Fast
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nWidth, nHeight, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, szGlyphData);
	//gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, nWidth, nHeight, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, szGlyphData);

	delete[] szGlyphData;

	// Create mesh data for the character

	// Work out tex coords
	float fTx = (float)bitmap->width / (float)nWidth;
	float fTy = (float)bitmap->rows / (float)nHeight;

	const vec2 texCoords[] =
	{
		vec2(0, fTy),
		vec2(fTx, fTy),
		vec2(fTx, 0),
		vec2(0, 0)
	};

	const vec3 vertices[] =
	{
		vec3(0, 0, 0),
		vec3((float)bitmap->width, 0, 0),
		vec3((float)bitmap->width, (float)bitmap->rows, 0),
		vec3(0, (float)bitmap->rows, 0)
	};

	const Triangle triangles[]=
	{
		Triangle(1, 3, 0),
		Triangle(1, 2, 3)
	};

	memcpy(face.mesh[ch].triangles, triangles, sizeof(Triangle) * 2);
	memcpy(face.mesh[ch].vertices, vertices, sizeof(vec3) * 4);
	memcpy(face.mesh[ch].texCoords, texCoords, sizeof(vec2) * 4);

	face.ulWidth[ch] = face.face->glyph->metrics.horiAdvance >> 6;
	face.ulHeight[ch] = face.face->glyph->metrics.vertAdvance >> 6;
	face.mesh[ch].texturePad = vec2(bitmapGlyph->left, bitmapGlyph->top-bitmap->rows);

	// Take into account texture padding for the "correct" width/height
	//face.ulHeight[ch] += (unsigned long)face.mesh[ch].texturePad.X();
	//face.ulWidth[ch] += (unsigned long)face.mesh[ch].texturePad.Y();

	// Create a display list for fast translating (padding) and drawing
	face.nLists[ch] = (int)glGenLists(1);
	glNewList(face.nLists[ch], GL_COMPILE);
	{
		glBindTexture(GL_TEXTURE_2D, (unsigned)face.nTextures[ch]);

		glPushMatrix();
		{
			// Texture padding, adjust position
			glTranslatef(face.mesh[ch].texturePad.X(), face.mesh[ch].texturePad.Y(), 0);

			glVertexPointer(3, GL_FLOAT, 0, face.mesh[ch].vertices);
			glTexCoordPointer(2, GL_FLOAT, 0, face.mesh[ch].texCoords);
			glDrawRangeElements(GL_TRIANGLES, 0, 5, 6, GL_UNSIGNED_SHORT, face.mesh[ch].triangles);
		}
		glPopMatrix();

		glTranslatef(face.ulWidth[ch], 0.0f, 0.0f);
	}
	glEndList();

	// Delete glyph information
	FT_Done_Glyph(glyph);

	return face.nLists[ch];
}

void GBFreeType::Render(const char* szText, const int nID)
{
	if(szText == NULL) return;
	if(!IsAvailable(nID)) return;

	std::string text = std::string(szText);

	// Reverse the text!
	if(m_faces[nID].pt < 0)
		reverse(text.begin(), text.end());

	// Vector of vectors to determine what character'(s) are colored
	//std::deque<ColorRange> colors;
	//ExtractColorValues(text.c_str(), colors);

	glPushClientAttrib(GL_ENABLE_BIT);

	glDisableClientState(GL_NORMAL_ARRAY);
	glDisableClientState(GL_INDEX_ARRAY);

	glEnable(GL_TEXTURE_2D);

	// Call the lists that are relative to each [i] Text
	// Save the old matrix
	glPushMatrix();
	{
		unsigned unStrLength = (unsigned)text.length();
		for(unsigned i = 0; i < unStrLength; i++) 
			glCallList(CacheGlyph(m_faces[nID], text[i]));
	}
	glPopMatrix();

	// Unbind any pre-existing bound textures
	glBindTexture(GL_TEXTURE_2D, -1);

	glPopAttrib();
}

int GBFreeType::GetPtSize(const int nID) const
{ 
	if(!IsAvailable(nID)) return 0;

	return m_faces[nID].pt;
}

float GBFreeType::GetGlyphWidth(const int nID, const char ch) const
{
	if(!IsAvailable(nID) || ch <= 0) return 0.0f;

	return (float)m_faces[nID].ulWidth[ch];
}

float GBFreeType::GetGlyphHeight(const int nID, const char ch) const
{
	if(!IsAvailable(nID) || ch <= 0) return 0.0f;

	return (float)m_faces[nID].ulHeight[ch];
}

const GBFreeType::GBCharMesh* GBFreeType::GetGlyphMesh(const int nID, const char ch) const
{
	if(!IsAvailable(nID) || ch <= 0) return NULL;

	return &m_faces[nID].mesh[ch];
}

float GBFreeType::GetTextWidth(const int nID, const char* szText) const
{
	if(!IsAvailable(nID) || szText == NULL) return 0.0f;

	float fLength = 0.0f;

	char* szOldText = (char*)szText;
	for (; *szOldText; szOldText++) 
		fLength += GetGlyphWidth(nID, *szOldText);

	return fLength;
}

float GBFreeType::GetTextHeight(const int nID, const char* szText) const
{
	if(!IsAvailable(nID) || szText == NULL) return 0.0f;

	float fHighest = 0.0f;
	int nNewLineCount = 0;

	char* szOldText = (char*)szText;
	for (; *szOldText; szOldText++) 
	{
		float fCurrent = GetGlyphHeight(nID, *szOldText);
		
		if(fCurrent > fHighest)
			fHighest = fCurrent;
	}

	return fHighest;
}

bool GBFreeType::IsAvailable(const int nID) const 
{ 
	if(nID < (int)m_faces.size() && nID >= 0)
		return (m_faces[nID].used);
	return false;
}